<?php
/**
 * response
 *
 * reponsing object
 *
 * @package:	radcore
 * @created:	8 oct 2008
 * @updated:	21 jan 2009
 * @version:	0.2b
 * @use:		singleton
 * @link:		http://radiance-project.com/
 * @link:		http://qfox.ru/
 * @author:		alex <alex@qfox.ru> yaroshevich
 * @copyright:	(c) 2008-2011 radiance framework
 */


function &respi(){return response::instance();}

class response extends singleton
{
	// php singleton wrapper
	static public function &instance() { return parent::instance(__CLASS__); }
	
	private $_started = null;
	
	// headers statuses
	private $hs = array(
		100 => '100 Continue',
		101 => '101 Switching Protocols',
		102 => '102 Processing',
		// ok codes
		200 => '200 OK',
		201 => '201 Created',
		202 => '202 Accepted',
		203 => '203 Non-Authoritative Information',
		204 => '204 No Content',
		205 => '205 Reset Content',
		206 => '206 Partial Content',
		207 => '207 Multi-Status',
		// 3xx redirects
		300 => '300 Multiple Choices',		// Затребованный URL обозначает более одного ресурса, и робот не смог однозначно определить, к какой странице URL относится
		301 => '301 Moved Permanently',		// Документ уже не используется сервером, а ссылка перенаправляет на другую страницу 
		302 => '302 Moved Temporarily',		// Запрошенный ресурс временно находится под другим адресом
		303 => '303 See Other',			// Запрошенный ресурс находится под другим адресом и его следует запрашивать, используя метод GET (получен код 303 See Other). Если вы хотите, чтобы указанная страница находилась в поиске, она должна отвечать кодом 200.
		304 => '304 Not Modified',		// Получен код 304 Not Modified. Если страница не изменилась с момента последнего обращения робота, рекомендуется выдавать этот код. Это ускорит индексацию и уменьшит трафик.
		305 => '305 Use Proxy',			// Доступ к затребованному ресурсу может осуществляться только через прокси-сервер, указанный в заголовке Location (получен код 305 Use Proxy).
		307 => '307 Temporary Redirect',	// Затребованный ресурс был временно переведен на другой адрес, который необходимо прописать в Location
		// 4xx client errors
		400 => '400 Bad Request',
		401 => '401 Authorization Required',
		402 => '402 Payment Required',
		403 => '403 Forbidden',
		404 => '404 Not Found',
		405 => '405 Method Not Allowed',
		406 => '406 Not Acceptable',
		407 => '407 Proxy Authentication Required',
		408 => '408 Request Time-out',
		409 => '409 Conflict',
		410 => '410 Gone',
		411 => '411 Length Required',
		412 => '412 Precondition Failed',
		413 => '413 Request Entity Too Large',
		414 => '414 Request-URI Too Large',
		415 => '415 Unsupported Media Type',
		416 => '416 Requested Range Not Satisfiable',
		417 => '417 Expectation Failed',
		418 => '418 I\'m a teapot',		// unused
		419 => '419 unused',
		420 => '420 unused',
		421 => '421 unused',
		422 => '422 Unprocessable Entity',
		423 => '423 Locked',
		424 => '424 Failed Dependency',
		425 => '425 No code',
		426 => '426 Upgrade Required',
		// 5xx server errors
		500 => '500 Internal Server Error',
		501 => '501 Method Not Implemented',
		502 => '502 Bad Gateway',
		503 => '503 Service Temporarily Unavailable',
		504 => '504 Gateway Time-out',
		505 => '505 HTTP Version Not Supported',
		506 => '506 Variant Also Negotiates',
		507 => '507 Insufficient Storage',
		508 => '508 unused',
		509 => '509 unused',
		510 => '510 Not Extended'
		);
	
	protected function __construct( )
	{
		if( $this ->_started ) return;
		ob_start( array( 'response', 'push' ) );
		$this ->_started = true;
	}
	
	public function __destruct( )
	{
		if( !$this->_started ) return;

		// destroy depending singletons
		singleton::destroy( 'visitor' );
		
		$this->flush_headers();
		// stop buffering
		while (@ob_end_flush());
		// and show content
		echo self::$content;
		
		$this ->_started = false;
	}
	
	private $headers = array( );
	
	// content
	private static $content = '';
	
	public static function cleanup() {
		self::$content = '';
	}
	
	public static function push($t) {
		self::$content .= $t;
	}
	
	
	/*
	public function header( $header )
	{
		array_push( $this->headers, $header );
	}
	$this ->headers[ $k ]
	*/
	
	
	// redirects
	private function __statuscode( $code )
	{
		if( is_null( $code ) ) $code = 302;
		if( !isset( $this ->hs[ $code ] ) ) die('broken sword.');
		
		$out = 'HTTP/1.1 '.$this ->hs[ $code ];
		header( $out );
	}
	
	private function __redirect( $path )
	{
		if( is_null( $path ) ) return false;
		header( sprintf( 'location: %s', $path ) );
		return true;
	}
	
	private function _redirect( $code = null, $path = null )
	{
		$this ->__statuscode( $code );
		$this ->__redirect( $path );
		
		$this ->cleanup( );
		exit( );
	}
	
	private function _showerrorcode( $code, $message = null )
	{
		function show( $code, $message = null )
		{
			$file = ERRORSPATH.$code.'.html';
			if( !is_readable( $file ) ) return false;
			echo file_get_contents( $file );
			if( !is_null( $message ) )
				echo "<hr/>".$message;
			return true;
		}
		
		$this ->__statuscode( $code );
		
		$guesses = array( $code, $code{0}.'xx', '500' );
		foreach( $guesses as $code )
		{
			if( show( $code, $message ) ) break;
		}
		
		$this ->cleanup( );
		exit( );
	}
	
	public function notfound( $message = null )
	{
		$this ->_showerrorcode( 404, $message );
	}
	
	public function moved( $path )
	{
		$this ->_redirect( 301, $path );
	}
	
	public function found( $path )
	{
		$this ->_redirect( 302, $path );
	}
	
	public function seeother( $path )
	{
		// отказаться от статуса 302 для клиентов с версией 1.1 в пользу статуса 303,
		// всегда добавлять в урл для редиректа параметры, позволяющие предотвратить непредсказуемую повторную отправку при редиректе (например, в чате, при редиректе <на самого себя>).
		$this ->_redirect( 303, $path );
	}
	
	public function notmodified( )
	{
		$this ->_redirect( 304 );
	}

	public function temporary( $path )
	{
		$this ->_redirect( 307, $path );
	}
	
	public function badrequest( )
	{
		$this ->_showerrorcode( 400 );
	}

	public function forbidden( )
	{
		$this ->_showerrorcode( 403 );
	}
	
	public function teapot( )
	{
		$this ->_showerrorcode( 418 );
	}
	
	public function wrongmethod( )
	{
		$this ->_showerrorcode( 501 );
	}
	
	public function done( )
	{
		if (bc::enabled()) bc::log( 'Manual stop.' );
		echo ob_get_clean( );
		exit;
	}
	
	/*public function html_redirect( $url, $message, $timeout=3 )
	{
		$env = $GLOBALS['env'];
		
		$env->template->url = $url;
		$env->template->message = $message;
		$env->template->timeout = $timeout;
		
		$this->page_content = $env->template->fetch('html_redirect.tpl');
		$this->send_response();
		exit();
	}//*/
	
	
	/**
	 * headers
	 *
	 * get, set for values
	 * store_Headers - set all filled headers
	 *
	 */
	public function __set( $k, $v ) {
		switch( $k ) {
		case 'content_type':
		case 'content_disposition':
		case 'charset':
			if( is_null( $v ) )
				unset( $this ->headers[ $k ] );
			else
				$this ->headers[ $k ] = $v;
			break;
		default:
			$this ->headers[ $k ] = $v;
		}
	}
	
	public function __get( $k ) {
		if( isset( $this ->headers[ $k ] ) )
			return $this ->headers[ $k ];
	}
	
	public function __isset( $k ) {
		return isset( $this ->headers[ $k ] );
	}

	public function __unset( $k ) {
		unset( $this ->headers[ $k ] );
	}

	public function &cookie($name, $value = null) {

		$cookie = array(
			'name' => '',
			'value' => null, 
			'expire' => null,
			'path' => null,
			'domain' => null,
			'secure' => null,
			'httponly' => null
		);

		$cookie['domain'] = config::instance()->cookie__domain;
		$cookie['path'] = config::instance()->cookie__path;

		if(is_array($name)) {
			$cookie = _::extract_args($name, $cookie);
		} else if(is_array($value)) {
			$cookie['name'] = $name;
			$cookie = _::extract_args($value, $cookie);
		} else {
			$cookie['name'] = $name;
			$cookie['value'] = $value;
		}

		extract($cookie, EXTR_OVERWRITE);

		setcookie($name, $value, $expire, $path, $domain, $secure, $httponly);

		return $this;
	}
	
	public function get_headers( )
	{
		return $this ->headers;
	}
	
	public function flush_headers( )
	{
		if( isset( $this ->content_type ) || isset( $this ->charset ) ) {
			if( !isset( $this ->content_type ) ) $this ->content_type = 'text/html';
			if( !isset( $this ->charset ) ) $this ->charset = 'utf-8';
			$istext = (substr( $this ->content_type, 0, 5 ) == 'text/');
			header( sprintf( $istext ? 'Content-type: %s;charset=%s' : 'Content-type: %s', $this ->content_type, $this ->charset ) );
		}
		if( isset( $this ->content_disposition ) ) {
			header( sprintf( 'Content-disposition: attachment; filename="%s"', $this ->content_disposition ) );
		}

		foreach( $this->headers as $k => $v ) {
			header( sprintf('%s: %s', $k, $v) );
		}

		$this ->headers = array( );
	}

	public function &expires( $date = null ) {
		if (!$date)
			$date = time();

		$this->headers['Expires'] = date('r', $date); // RFC 2822 formatted date 
		$this->nocache();

		return $this;
	}

	public function &nocache() {
		header('Cache-Control: no-store, no-cache, must-revalidate');
		header('Cache-Control: post-check=0, pre-check=0', false); 
		header('Pragma: no-cache');

		return $this;
	}

}

